﻿using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Types;
using MyFloridaHouse.libSvDTO;
using SQLSpatialTools;
using MyFloridaHouse.libSvCommonUtilities;

namespace MyFloridaHouse.DataAccess.SQLServerTypes
{
    // the point of this class is to isolate all sqlGeometry use in the dataAccess library, since we'll probably be switching soon
    public static class TOOLS_SQLGeometry
    {

        public static MapMultiShapeDTO ToMapMultiShapeDTO(SqlGeometry sqlGeom)
        {
            MapMultiShapeDTO mapShape = new MapMultiShapeDTO();
            MapMultiShapeType newShapeType = MapMultiShapeType.Empty;
            BuildMapMultiShapeDTOEntry(sqlGeom, out newShapeType, mapShape.shapeList);
            mapShape.shapeType = newShapeType;
            return mapShape;
        }

        public static void BuildMapMultiShapeDTOEntry(SqlGeometry sqlGeom, out MapMultiShapeType parentShapeType, List<MapMultiShapeEntryDTO> mapShapeList)
        {
            MapMultiShapeEntryDTO newMapShape = new MapMultiShapeEntryDTO();

            parentShapeType = MapMultiShapeType.Empty;
            string sqlShapeType = sqlGeom.STGeometryType().ToString().ToUpper();

            if (sqlShapeType == MapMultiShapeType.Polygon.ToString().ToUpper())
            {
                parentShapeType = MapMultiShapeType.Polygon;
                mapShapeList.Add(ToMapMultiShapePolygon(sqlGeom));
            }

            else if (sqlShapeType == MapMultiShapeType.MultiPolygon.ToString().ToUpper())
            {
                parentShapeType = MapMultiShapeType.MultiPolygon;

                int shapeCount = (int)sqlGeom.STNumGeometries();
                for (int shapeIndex = 1; shapeIndex <= shapeCount; shapeIndex++)
                {
                    MapMultiShapeType subShapeType = MapMultiShapeType.Empty; // this is ignored for the sub shapes
                    SqlGeometry sqlSubShape = sqlGeom.STGeometryN(shapeIndex);
                    BuildMapMultiShapeDTOEntry(sqlSubShape, out subShapeType, mapShapeList);
                }
            }

            else if (sqlShapeType == MapMultiShapeType.LineString.ToString().ToUpper())
            {
                parentShapeType = MapMultiShapeType.LineString;
                mapShapeList.Add(ToMapMultiShapeLineString(sqlGeom));
            }

            else if (sqlShapeType == MapMultiShapeType.MultiLineString.ToString().ToUpper())
            {
                parentShapeType = MapMultiShapeType.MultiLineString;

                int shapeCount = (int)sqlGeom.STNumGeometries();
                for (int shapeIndex = 1; shapeIndex <= shapeCount; shapeIndex++)
                {
                    MapMultiShapeType subShapeType = MapMultiShapeType.Empty; // this is ignored for the sub shapes
                    SqlGeometry sqlSubShape = sqlGeom.STGeometryN(shapeIndex);
                    BuildMapMultiShapeDTOEntry(sqlSubShape, out subShapeType, mapShapeList);
                }
            }

            else if (sqlShapeType == MapMultiShapeType.Point.ToString().ToUpper())
            {
                parentShapeType = MapMultiShapeType.Point;
                mapShapeList.Add(ToMapMultiShapePoint(sqlGeom));
            }

            else if (sqlShapeType == MapMultiShapeType.GeometryCollection.ToString().ToUpper())
            {
                parentShapeType = MapMultiShapeType.GeometryCollection;

                int shapeCount = (int)sqlGeom.STNumGeometries();
                for (int shapeIndex = 1; shapeIndex <= shapeCount; shapeIndex++)
                {
                    MapMultiShapeType subShapeType = MapMultiShapeType.Empty; // this is ignored for the sub shapes
                    SqlGeometry sqlSubShape = sqlGeom.STGeometryN(shapeIndex);
                    BuildMapMultiShapeDTOEntry(sqlSubShape, out subShapeType, mapShapeList);
                }

            }

            else
            {
                // ignore the shape, we don't understand it
                //todo: Log that SQLGeometryTools.ToMapMultiShapeDTO can't figure out what shape a sqlGeometry is
            }

            return;
        }

        private static MapMultiShapeEntryDTO ToMapMultiShapePolygon(SqlGeometry sqlGeom)
        {
            MapMultiShapeEntryDTO shape = new MapMultiShapeEntryDTO();
            shape.shapeType = MapMultiShapeType.Polygon;
            shape.ExteriorRing = GetExteriorRing(sqlGeom);
            shape.InteriorRings = GetInteriorRings(sqlGeom);
            return shape;
        }

        private static MapMultiShapeEntryDTO ToMapMultiShapeLineString(SqlGeometry sqlGeom)
        {
            MapMultiShapeEntryDTO shape = new MapMultiShapeEntryDTO();
            shape.shapeType = MapMultiShapeType.LineString;
            shape.ExteriorRing = GetLineString(sqlGeom);
            return shape;
        }

        private static MapMultiShapeEntryDTO ToMapMultiShapePoint(SqlGeometry sqlGeom)
        {
            MapMultiShapeEntryDTO shape = new MapMultiShapeEntryDTO();
            shape.shapeType = MapMultiShapeType.Point;
            shape.ExteriorRing = GetExteriorRing(sqlGeom);
            return shape;
        }


        private static List<PointDTO> GetExteriorRing(SqlGeometry sqlGeom)
        {
            List<PointDTO> newExteriorRing = new List<PointDTO>();
            SqlGeometry exteriorRing = sqlGeom.STExteriorRing();
            if (exteriorRing.STNumPoints() > 2)
            {
                for (int pIndex = 1; pIndex <= (int)exteriorRing.STNumPoints(); pIndex++)
                {
                    newExteriorRing.Add(new PointDTO((double)exteriorRing.STPointN(pIndex).STY, (double)exteriorRing.STPointN(pIndex).STX));
                }
            }
            return newExteriorRing;
        }

        private static List<List<PointDTO>> GetInteriorRings(SqlGeometry sqlGeom)
        {
            List<List<PointDTO>> newInteriorRingsList = new List<List<PointDTO>>();

            // now include the interior rings (if any)
            int interiorRingCount = (int)sqlGeom.STNumInteriorRing();
            for (int ringIndex = 1; ringIndex <= interiorRingCount; ringIndex++)
            {
                SqlGeometry interiorRing = sqlGeom.STInteriorRingN(ringIndex);
                if (interiorRing.STNumPoints() > 2)
                {
                    List<PointDTO> newInteriorList = new List<PointDTO>();
                    for (int pIndex = 1; pIndex <= (int)interiorRing.STNumPoints(); pIndex++)
                    {
                        newInteriorList.Add(new PointDTO((double)interiorRing.STPointN(pIndex).STY, (double)interiorRing.STPointN(pIndex).STX));
                    }
                    newInteriorRingsList.Add(newInteriorList);
                }
            }
            return newInteriorRingsList;
        }

        private static List<PointDTO> GetLineString(SqlGeometry sqlGeom)
        {
            List<PointDTO> newLineStringPointList = new List<PointDTO>();

            for (int pIndex = 1; pIndex <= (int)sqlGeom.STNumPoints(); pIndex++)
            {
                newLineStringPointList.Add(new PointDTO((double)sqlGeom.STPointN(pIndex).STY, (double)sqlGeom.STPointN(pIndex).STX));
            }

            return newLineStringPointList;
        }


        public static SqlGeometry BuildShape(MapMultiShapeDTO mapShape)
        {
            List<string> wkt = new List<string>();

            if (mapShape.shapeType == MapMultiShapeType.Polygon)
            {
                wkt.Add("POLYGON((");

                List<string> exteriorPointList = new List<string>();
                foreach (var pnt in mapShape.shapeList[0].ExteriorRing)
                {
                    exteriorPointList.Add(pnt.longitude + " " + pnt.latitude);
                }
                wkt.Add(string.Join(",", exteriorPointList));

                wkt.Add("))");

            }
            else
            {
                throw new Exception("BuildShape failed, unexpected shape type " + mapShape.shapeType.ToString());

                //TODO: Add other shapes if needed, right now only need polygon for bounding box
                
            }


            string newShapeWkt = string.Join(" ", wkt);
            return SqlGeometry.STGeomFromText(new SqlChars(newShapeWkt), Constants.GeometryShapeProjectionCode);

        }


        // coordinates to box and back again from http://barendgehrels.blogspot.com/2011/05/sqlgeometry-and-boxes.html

        //public static SqlGeometry CoordinatesToBox(double west, double south, double east, double north, int srid)        
        //{           
        //    SqlGeometryBuilder builder = new SqlGeometryBuilder();
        //    builder.SetSrid(srid);           
        //    builder.BeginGeometry(OpenGisGeometryType.Polygon);
        //    builder.BeginFigure(west, south);
        //    builder.AddLine(west, north);
        //    builder.AddLine(east, north);
        //    builder.AddLine(east, south);
        //    builder.AddLine(west, south); // Yes, we close it neatly
        //    builder.EndFigure();           
        //    builder.EndGeometry();           
        //    return builder.ConstructedGeometry;        
        //}        

        //public static void BoxToCoordinates(SqlGeometry box, out double west, out double south, out double east, out double north)        
        //{           
        //    SqlGeometry lower_left = box.STPointN(1);           
        //    SqlGeometry upper_right = box.STPointN(3);          
        //    west = lower_left.STX.Value;
        //    south = lower_left.STY.Value;
        //    east = upper_right.STX.Value;
        //    north = upper_right.STY.Value;
        //    // Defensive programming:           
        //    MakeFirstSmaller(ref west, ref east);           
        //    MakeFirstSmaller(ref south, ref north);        
        //}

        private static void MakeFirstSmaller<T>(ref T a, ref T b) where T : IComparable
        {
            if (a.CompareTo(b) > 0)
            {
                // Exchange the two values               
                T t = a;
                a = b;
                b = t;
            }
        }

        public static BoundingBoxDTO GeometrySTEnvelopeToBox(SqlGeometry box)
        {
            box.MakeValid();
            SqlGeometry lower_left = box.STPointN(1);
            SqlGeometry upper_right = box.STPointN(3);
            double west = lower_left.STX.Value;
            double south = lower_left.STY.Value;
            double east = upper_right.STX.Value;
            double north = upper_right.STY.Value;
            // Defensive programming:           
            MakeFirstSmaller(ref west, ref east);
            MakeFirstSmaller(ref south, ref north);

            BoundingBoxDTO result = new BoundingBoxDTO(north, west, south, east);

            return result;
        }

        public static SqlGeometry BuildBoxFromPoint(double latitude, double longitude, double offset)
        {
            double north = latitude + offset;
            double south = latitude - offset;

            double east = longitude - offset;
            double west = longitude + offset;

            SqlGeometry newGeom = BuildBoxFromCorners(north, south, east, west);

            return newGeom;

        }


        public static SqlGeometry BuildBoxFromBoundingBoxDTO(BoundingBoxDTO boundingBox)
        {
            double north = boundingBox.northWest.latitude;
            double south = boundingBox.southEast.latitude;

            double east = boundingBox.northWest.longitude;
            double west = boundingBox.southEast.longitude;

            SqlGeometry newGeom = BuildBoxFromCorners(north, south, east, west);

            return newGeom;
        }

        public static SqlGeometry BuildBoxFromCorners(double north, double south, double east, double west)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("POLYGON((");
            sb.Append(west.ToString() + " " + south.ToString() + ",");
            sb.Append(east.ToString() + " " + south.ToString() + ",");
            sb.Append(east.ToString() + " " + north.ToString() + ",");
            sb.Append(west.ToString() + " " + north.ToString() + ",");
            sb.Append(west.ToString() + " " + south.ToString());
            sb.Append("))");

            SqlChars sc = new SqlChars(sb.ToString());
            SqlGeometry newGeom = SqlGeometry.STGeomFromText(sc, Constants.GeometryShapeProjectionCode);
            return newGeom;
        }

        public static SqlGeometry AggregateShapes(List<SqlGeometry> geometryList)
        {

            List<SqlGeometry> iGeometryList;
            iGeometryList = geometryList;
            SqlGeometry geom = new SqlGeometry();
            GeometryCollectionAggregate CollAggregate = new GeometryCollectionAggregate();
            CollAggregate.Init();
            for (int listIndex = 0; listIndex < geometryList.Count; )
            {
                CollAggregate.Accumulate(geometryList[listIndex]);
                listIndex++;
            }
            geom = CollAggregate.Terminate();
            geom = geom.STUnion(geom);

            SqlGeometry iGeom = new SqlGeometry();
            iGeom = geom;

            List<Polygon> polygons = PolygonTools.GetPolygons(geom, 20);
            List<Polygon> polygonsCleaned = PolygonTools.CleanPolygons(polygons);
            SqlGeometry newGeom = TOOLS_SQLGeometry.CreateNewGeomFromReducedShape(polygonsCleaned);
            geom = newGeom.MakeValid();

            if ((geom.STGeometryType() == "POLYGON")) //filter artifacts doesn't seem to handle multipolys
            {
                // TODO:  This is removing valid interior rings, so disable till function is fixed
                //geom = Functions.FilterArtifactsGeometry(geom, true, true, 100, .08);
            }

            if (geom.IsNull) //SpatialTools's sent back a null
            {
                geom = TOOLS_SQLGeometry.UnionSQLGeometries(iGeometryList);
                //geom = Functions.FilterArtifactsGeometry(geom, true, true, 100, .08);
            }
            //Filter Artifacts returned an empty object so we will
            //remove slivers the old fashioned way
            if (geom.ToString().Contains("EMPTY"))
            {
                //geom = iGeom.BufferWithTolerance(1, 1, true).BufferWithTolerance(-2 * 1, 1, true).STUnion(iGeom); //this times out...
                //geom = RemoveSlivers(iGeom); //this doesn't hand small blocks... 
                geom = iGeom;
            }

            return geom;
        }



        /// <summary>
        /// Gets the label location.
        /// See the sql spatial fourum at http://social.msdn.microsoft.com/Forums/en-US/sqlspatial/thread/4e9f687e-ee26-437a-9b71-765d381171b6
        /// The methodology used is to get the two lines that intersect the shape centered on the bounding box
        /// and determine which one is the best fit.  If not best fit is found within the shape based on this method
        /// then the centroid is returned
        /// </summary>
        /// <param name="subShapeGeom">The geom.</param>
        /// <returns></returns>
        public static PointDTO GetLabelLocation(SqlGeometry geom)
        {
            const string veryShortLineWKT = "LINESTRING(0.0 0.0 , -0.0000000005 0.0) ";
            SqlGeometry labelPointGeom = new SqlGeometry();

            try
            {
                SqlGeometry latIntersectLine = SqlGeometry.STGeomFromText(new SqlChars(veryShortLineWKT), Constants.GeometryShapeProjectionCode);
                SqlGeometry lonIntersectLine = SqlGeometry.STGeomFromText(new SqlChars(veryShortLineWKT), Constants.GeometryShapeProjectionCode);

                for (int subShapeIndex = 1; subShapeIndex <= geom.STNumGeometries(); subShapeIndex++)
                {
                    SqlGeometry subShapeGeom = geom.STGeometryN(subShapeIndex);

                    double NWLat;
                    double NWLon;
                    double SELat;
                    double SELon;
                    ExtractPointsFromEnvelope(subShapeGeom, out NWLat, out NWLon, out SELat, out SELon);

                    var midLat = ((NWLat - SELat) / 2) + SELat;
                    var midLon = ((NWLon - SELon) / 2) + SELon;

                    // 
                    latIntersectLine = GetLongestGeometryIntersectingLine(latIntersectLine, subShapeGeom, NWLon, SELon, midLat, midLat);
                    lonIntersectLine = GetLongestGeometryIntersectingLine(lonIntersectLine, subShapeGeom, midLon, SELat, midLon, SELat);

                }


                if (latIntersectLine.STLength() > lonIntersectLine.STLength())
                {
                    labelPointGeom = BuildGeometryLabelPoint(geom, latIntersectLine);

                }
                else
                {
                    labelPointGeom = BuildGeometryLabelPoint(geom, lonIntersectLine);
                }

                if (!geom.STContains(labelPointGeom))
                {
                    labelPointGeom = geom.STPointOnSurface();
                }
            }
            catch (Exception ex)
            {
                try
                {
                    labelPointGeom = geom.STPointOnSurface();
                }
                catch
                {
                    labelPointGeom = SqlGeometry.STGeomFromText(new SqlChars("POINT(0.0 0.0)"), Constants.GeometryShapeProjectionCode);
                }
            }

            PointDTO labelLocation = new PointDTO();
            labelLocation.latitude = 0.0;
            labelLocation.longitude = 0.0;
            if (labelPointGeom.STNumPoints() > 0)
            {
                labelLocation.latitude = (double)labelPointGeom.STPointN(1).STY;
                labelLocation.longitude = (double)labelPointGeom.STPointN(1).STX;
            }
            return labelLocation;

        }

        private static SqlGeometry BuildGeometryLabelPoint(SqlGeometry geom, SqlGeometry intersectLine)
        {
            double pointLat = (((double)intersectLine.STStartPoint().STY - (double)intersectLine.STEndPoint().STY) / 2) + (double)intersectLine.STEndPoint().STY;
            double pointLon = (((double)intersectLine.STStartPoint().STX - (double)intersectLine.STEndPoint().STX) / 2) + (double)intersectLine.STEndPoint().STX;
            SqlGeometry labelPtGeom = SqlGeometry.STGeomFromText(new SqlChars("POINT(" + pointLon + " " + pointLat + ")"), Constants.GeometryShapeProjectionCode);
            if (labelPtGeom.STWithin(geom))
            {
                return labelPtGeom;
            }
            else
            {
                return geom.STCentroid();
            }     
        }

        private static SqlGeometry GetLongestGeometryIntersectingLine(SqlGeometry longestLine, SqlGeometry geom, double lonTop, double lonBottom, double latLeft, double latRight)
        {
            SqlGeometry LatLineIntersect = longestLine;
            double longestLineMeasure = (double)longestLine.STLength();

            try
            {
                var sc = new SqlChars("LINESTRING(" + lonTop + " " + latLeft + ", " + lonBottom + " " + latRight + ")");
                SqlGeometry latLine = SqlGeometry.STGeomFromText(sc, Constants.GeometryShapeProjectionCode);


                // get all parts of the line that crosses the shape, it could cross multiple times
                SqlGeometry newLineIntersect = latLine.STIntersection(geom);
                for (int subShapeIndex = 1; subShapeIndex <= geom.STNumGeometries(); subShapeIndex++)
                {
                    SqlGeometry subShapeGeom = geom.STGeometryN(subShapeIndex);


                    if (newLineIntersect.STLength() > longestLineMeasure)
                    {
                        LatLineIntersect = newLineIntersect;
                        longestLineMeasure = (double)newLineIntersect.STLength();
                    }
                }
            }
            catch
            {
            }
            return LatLineIntersect;
        }

        private static void ExtractPointsFromEnvelope(SqlGeometry geom, out double NWLat, out double NWLon, out double SELat, out double SELon)
        {
            SqlGeometry boundingBox = geom.STEnvelope();
            //First find the latitudinal label point
            NWLat = (double)boundingBox.STPointN(4).STY;
            NWLon = (double)boundingBox.STPointN(4).STX;
            SELat = (double)boundingBox.STPointN(2).STY;
            SELon = (double)boundingBox.STPointN(2).STX;
        }


        public static SqlGeometry CreateNewGeomFromReducedShape(List<Polygon> polygonsCleaned)
        {
            SqlGeometry newGeom = new SqlGeometry();
            StringBuilder sb = new StringBuilder();
            if (polygonsCleaned.Count() > 1)
            {
                sb.Append("MULTIPOLYGON((");
            }
            else if (polygonsCleaned.Count() == 1)// single poly
            {
                sb.Append("POLYGON(");
            }
            else //is empty return empty geometry
            {
                return newGeom;
            }
            int polyInx = 0;
            foreach (Polygon poly in polygonsCleaned)
            {
                if (polyInx == 0)
                {
                    sb.Append("(");
                }
                else
                {
                    sb.Append(",(");
                }
                int ptInx = 01;
                foreach (SqlGeometry pt in poly.polyPoints) //exterior
                {
                    if (ptInx < poly.polyPoints.Count)
                    {
                        sb.Append(pt.STX.ToString() + " " + pt.STY.ToString() + ",");
                    }
                    else //last one no comma
                    {
                        sb.Append(pt.STX.ToString() + " " + pt.STY.ToString());
                    }
                    ptInx++;
                }
                sb.Append(")");
                foreach (interiorRing ir in poly.irRingList)
                {
                    sb.Append(",(");
                    ptInx = 01;
                    foreach (SqlGeometry pt in ir.irPoints)
                    {
                        if (ptInx < ir.irPoints.Count)
                        {
                            sb.Append(pt.STX.ToString() + " " + pt.STY.ToString() + ",");
                        }
                        else //last one no comma
                        {
                            sb.Append(pt.STX.ToString() + " " + pt.STY.ToString());
                        }
                        ptInx++;
                    }
                    sb.Append(")");
                }
                polyInx++;
            }
            if (polygonsCleaned.Count() > 1)
            {
                sb.Append("))");
            }
            else // single poly
            {
                sb.Append(")");
            }
            SqlChars sc = new SqlChars(sb.ToString());
            newGeom = SqlGeometry.STGeomFromText(sc, Constants.GeometryShapeProjectionCode);

            return newGeom;
        }


        public static SqlGeometry UnionSQLGeometries(List<SqlGeometry> geometryList)
        {
            List<SqlGeometry> unionedGeometryList = new List<SqlGeometry>();

            int batchSize = 2;
            for (int listIndex = 0; listIndex < geometryList.Count; )
            {
                SqlGeometry unionedGeometry = new SqlGeometry();

                for (int j = 0; j < batchSize; j++)
                {
                    if (listIndex >= geometryList.Count)
                    {
                        break;
                    }
                    else
                    {
                        if (j == 0)
                        {
                            unionedGeometry = geometryList[listIndex];
                        }
                        else
                        {
                            unionedGeometry = unionedGeometry.STUnion(geometryList[listIndex]);
                        }
                        listIndex++;
                    }
                }
                unionedGeometryList.Add(unionedGeometry);
            }

            if (unionedGeometryList.Count > 1)
            {
                return UnionSQLGeometries(unionedGeometryList);
            }
            else if (unionedGeometryList.Count == 1)
            {
                return unionedGeometryList.First();
            }
            else
            {
                // got here because we were given an empty list, so return an empty shape
                return new SqlGeometry();
            }
        }

        public static SqlGeometry RemoveSlivers(SqlGeometry geom)
        {
            SqlGeometry finalGeom = new SqlGeometry();

            int includedShapeCount = 0;

            //debug
            int geometryCount = (int)geom.STNumGeometries();

            // this could be a Geometry collection, MultiPolygon, Polygon, MultiPolyline(unlikely), Polyline(unlikely), MultiPoint(unlikely) or Point (unlikely)
            for (int geomIndex = 1; geomIndex <= geom.STNumGeometries(); geomIndex++)
            {
                SqlGeometry childGeom = geom.STGeometryN(geomIndex);

                //debug

                string childGeomType = (string)childGeom.STGeometryType();
                double childGeomArea = (double)childGeom.STArea();

                if (((string)childGeom.STGeometryType() == "GeometryCollection")
                    | ((string)childGeom.STGeometryType() == "MultiPolygon")
                    | ((string)childGeom.STGeometryType() == "Polygon"))
                {
                    SqlGeometry newGeom = new SqlGeometry();

                    if (childGeom.STNumGeometries() == 1)
                    {
                        newGeom = RemoveGeometryInteriorRings(childGeom);
                    }
                    else
                    {
                        newGeom = RemoveSlivers(childGeom);
                    }

                    SqlGeometry tempGeom;
                    if (includedShapeCount == 0)
                    {
                        tempGeom = newGeom;
                        includedShapeCount++;
                    }
                    else
                    {
                        tempGeom = finalGeom.STUnion(newGeom);
                    }

                    if (tempGeom.IsNull)
                    {
                        // STUnion returned null
                        finalGeom = geom;
                    }
                    else
                    {
                        finalGeom = tempGeom;
                    }
                }
            }
            return finalGeom;
        }

        public static SqlGeometry RemoveGeometryInteriorRings(SqlGeometry geom)
        {
            // stupid exterior "ring" come out as a stupid linestring - need to turn it into a stupid polygon - one stupid point at a time
            SqlGeometry finalGeom = MakeValidPolygon(geom.STExteriorRing());

            bool val = (bool)finalGeom.STIsValid();
            string txt = finalGeom.ToString();

            // now need to include any interior rings that are not insignificant slivers
            int interiorRingCount = (int)geom.STNumInteriorRing();
            for (int ringIndex = 1; ringIndex <= interiorRingCount; ringIndex++)
            {
                // all interior rings come out as linestrings too, need to make them polygons
                if (geom.STInteriorRingN(ringIndex).STNumPoints() > 2)
                {
                    SqlGeometry ring = MakeValidPolygon(geom.STInteriorRingN(ringIndex));
                    SqlGeography ringGeog = SqlGeography.STGeomFromText(ring.STAsText(), Constants.GeometryShapeProjectionCode);
                    double ringArea = (double)ringGeog.STArea();
                    if (ringArea > .0000001)
                    {
                        finalGeom = finalGeom.STDifference(ring);
                    }
                    else
                    {
                        // ha, dropped a sliver!
                        //int i = 1;
                    }
                }
            }

            return finalGeom;
        }


        private static SqlGeometry MakeValidPolygon(SqlGeometry ring)
        {
            SqlGeometryBuilder gb = new SqlGeometryBuilder();
            gb.SetSrid(Constants.GeometryShapeProjectionCode);
            gb.BeginGeometry(OpenGisGeometryType.Polygon);

            int startIndex = 2;
            int endIndex = (int)ring.STNumPoints();
            int step = 1;
            double startPointX = (double)ring.STStartPoint().STX;
            double startPointY = (double)ring.STStartPoint().STY;
            gb.BeginFigure(startPointX, startPointY);
            for (int pIndex = startIndex; pIndex <= endIndex; pIndex += step)
            {
                gb.AddLine((double)ring.STPointN(pIndex).STX, (double)ring.STPointN(pIndex).STY);
            }
            gb.AddLine(startPointX, startPointY);
            gb.EndFigure();
            gb.EndGeometry();
            return gb.ConstructedGeometry.MakeValid();
        }


        //private static List<PointDTO> RemoveDuplicatePoints(List<PointDTO> pointList)
        //{
        //    const int comparisonInt = 100000;
        //    int ndx = 0;
        //    while (ndx < pointList.Count - 1)
        //    {
        //        if (
        //             (Convert.ToInt32(pointList[ndx].latitude*comparisonInt) == (Convert.ToInt32(pointList[ndx + 1].latitude*comparisonInt)))
        //            &(Convert.ToInt32(pointList[ndx].longitude*comparisonInt) == (Convert.ToInt32(pointList[ndx + 1].longitude*comparisonInt)))
        //           )
        //        {
        //            pointList.RemoveAt(ndx);
        //        }
        //        else
        //        {
        //            ndx++;
        //        }

        //    }
        //    return pointList;
        //}



    }
}
